package com.castle.fortress.admin.flowable.cmd;

import cn.hutool.core.util.StrUtil;
import com.castle.fortress.admin.flowable.utils.FlowableUtils;
import com.castle.fortress.common.exception.BizException;
import com.castle.fortress.common.respcode.BizErrorCode;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.cmd.AbstractDynamicInjectionCmd;
import org.flowable.engine.impl.dynamic.BaseDynamicSubProcessInjectUtil;
import org.flowable.engine.impl.dynamic.DynamicUserTaskBuilder;
import org.flowable.engine.impl.persistence.entity.DeploymentEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.task.service.TaskService;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;

import java.util.*;

/**
 * 个性化 动态添加实例节点(单人)
 */
public class FortressInjectSingleUserTaskInProcessInstanceCmd extends AbstractDynamicInjectionCmd implements Command<Void> {
    protected String processInstanceId;
    protected DynamicUserTaskBuilder dynamicUserTaskBuilder;
    //流程中的参照节点的ID
    private org.flowable.task.api.Task referTask;
    //相对参照节点的节点位置类型 00 前 01 后
    private String referLocationType;
    //assigneeUsers
    private String assignee;

    public FortressInjectSingleUserTaskInProcessInstanceCmd(String processInstanceId, DynamicUserTaskBuilder dynamicUserTaskBuilder, org.flowable.task.api.Task referTask , String referLocationType, String assignee) {
        this.processInstanceId = processInstanceId;
        this.dynamicUserTaskBuilder = dynamicUserTaskBuilder;
        this.referTask= referTask;
        this.referLocationType  = StrUtil.isEmpty(referLocationType)?"00":referLocationType;
        this.assignee = assignee;
    }

    public Void execute(CommandContext commandContext) {
        this.createDerivedProcessDefinitionForProcessInstance(commandContext, this.processInstanceId);
        return null;
    }

    protected void updateBpmnProcess(CommandContext commandContext, Process process, BpmnModel bpmnModel, ProcessDefinitionEntity originalProcessDefinitionEntity, DeploymentEntity newDeploymentEntity) {
        List<StartEvent> startEvents = process.findFlowElementsOfType(StartEvent.class);
        StartEvent initialStartEvent = null;
        Iterator var8 = startEvents.iterator();

        while(var8.hasNext()) {
            StartEvent startEvent = (StartEvent)var8.next();
            if (startEvent.getEventDefinitions().size() == 0) {
                initialStartEvent = startEvent;
                break;
            }

            if (initialStartEvent == null) {
                initialStartEvent = startEvent;
            }
        }
        if(referTask!=null){
            TaskService taskService = CommandContextUtil.getTaskService(commandContext);
            TaskEntity taskEntity = taskService.getTask(referTask.getId());
            if(taskEntity == null){
                throw new BizException(BizErrorCode.TASK_ID_ERROR);
            }
            ExecutionEntity executionEntity = CommandContextUtil.getExecutionEntityManager(commandContext).findById(taskEntity.getExecutionId());
            if(executionEntity == null){
                throw new BizException(BizErrorCode.TASK_ID_ERROR);
            }
            FlowElement flowElement = process.getFlowElement(executionEntity.getActivityId(),true);
            Activity currentActivity = (Activity) flowElement;
            //初始化taskId
            String id = dynamicUserTaskBuilder.getId();
            if(id == null){
                id = dynamicUserTaskBuilder.nextTaskId(process.getFlowElementMap());
                dynamicUserTaskBuilder.setId(id);
            }
            dynamicUserTaskBuilder.setDynamicTaskId(id);
            //初始化节点信息
            UserTask addUserTask = initUserTask(flowElement);
            FlowableUtils.insertToProcess(process,addUserTask,flowElement,"pre");
//            process.addFlowElement(addUserTask);
            //前加节点
            if("00".equals(this.referLocationType)){
                List<SequenceFlow> sequenceFlows = currentActivity.getIncomingFlows();
                for(SequenceFlow currentInFlow:sequenceFlows){
                    //获取当前节点的流入线
//                    SequenceFlow currentInFlow =  currentActivity.getIncomingFlows().get(0);
                    //将流入线的目标节点切换为新节点
                    currentInFlow.setTargetRef(addUserTask.getId());
                    currentInFlow.setTargetFlowElement(addUserTask);
                }
                //创建新的连接线 连接新节点与当前节点
                String preSequenceFlowId= sequenceFlows.get(0).getId()+System.currentTimeMillis()+"_pre";
                SequenceFlow preSequenceFlow = initSequenceFlow(preSequenceFlowId,addUserTask.getId(),flowElement.getId(),addUserTask,flowElement);
                FlowableUtils.insertToProcess(process,preSequenceFlow,flowElement,"pre");
//                process.addFlowElement(preSequenceFlow);
                currentActivity.setIncomingFlows(Collections.singletonList(preSequenceFlow));

            //后加节点
            }else{
                List<SequenceFlow> sequenceFlows = currentActivity.getOutgoingFlows();
                for(SequenceFlow currentInFlow:sequenceFlows){
                    //获取当前节点的流入线
//                    SequenceFlow currentInFlow =  currentActivity.getIncomingFlows().get(0);
                    //将流入线的目标节点切换为新节点
                    currentInFlow.setSourceRef(addUserTask.getId());
                    currentInFlow.setSourceFlowElement(addUserTask);
                }
                //创建新的连接线 连接新节点与当前节点
                String nextSequenceFlowId= sequenceFlows.get(0).getId()+System.currentTimeMillis()+"_next";
                SequenceFlow nextSequenceFlow = initSequenceFlow(nextSequenceFlowId,flowElement.getId(),addUserTask.getId(),flowElement,addUserTask);
                FlowableUtils.insertToProcess(process,nextSequenceFlow,flowElement,"next");
//                process.addFlowElement(preSequenceFlow);
                currentActivity.setOutgoingFlows(Collections.singletonList(nextSequenceFlow));
            }
            bpmnModel.getLocationMap().clear();
            bpmnModel.getFlowLocationMap().clear();
            bpmnModel.getLabelLocationMap().clear();

        }else{
            ParallelGateway parallelGateway = new ParallelGateway();
            parallelGateway.setId(this.dynamicUserTaskBuilder.nextForkGatewayId(process.getFlowElementMap()));
            process.addFlowElement(parallelGateway);
            UserTask userTask = new UserTask();
            if (this.dynamicUserTaskBuilder.getId() != null) {
                userTask.setId(this.dynamicUserTaskBuilder.getId());
            } else {
                userTask.setId(this.dynamicUserTaskBuilder.nextTaskId(process.getFlowElementMap()));
            }

            this.dynamicUserTaskBuilder.setDynamicTaskId(userTask.getId());
            userTask.setName(this.dynamicUserTaskBuilder.getName());
            userTask.setAssignee(this.dynamicUserTaskBuilder.getAssignee());
            process.addFlowElement(userTask);
            EndEvent endEvent = new EndEvent();
            endEvent.setId(this.dynamicUserTaskBuilder.nextEndEventId(process.getFlowElementMap()));
            process.addFlowElement(endEvent);
            SequenceFlow flowToUserTask = new SequenceFlow(parallelGateway.getId(), userTask.getId());
            flowToUserTask.setId(this.dynamicUserTaskBuilder.nextFlowId(process.getFlowElementMap()));
            process.addFlowElement(flowToUserTask);
            SequenceFlow flowFromUserTask = new SequenceFlow(userTask.getId(), endEvent.getId());
            flowFromUserTask.setId(this.dynamicUserTaskBuilder.nextFlowId(process.getFlowElementMap()));
            process.addFlowElement(flowFromUserTask);
            SequenceFlow initialFlow = (SequenceFlow)initialStartEvent.getOutgoingFlows().get(0);
            initialFlow.setSourceRef(parallelGateway.getId());
            SequenceFlow flowFromStart = new SequenceFlow(initialStartEvent.getId(), parallelGateway.getId());
            flowFromStart.setId(this.dynamicUserTaskBuilder.nextFlowId(process.getFlowElementMap()));
            process.addFlowElement(flowFromStart);
            GraphicInfo elementGraphicInfo = bpmnModel.getGraphicInfo(initialStartEvent.getId());
            if (elementGraphicInfo != null) {
                double yDiff = 0.0;
                double xDiff = 80.0;
                if (elementGraphicInfo.getY() < 173.0) {
                    yDiff = 173.0 - elementGraphicInfo.getY();
                    elementGraphicInfo.setY(173.0);
                }

                Map<String, GraphicInfo> locationMap = bpmnModel.getLocationMap();
                Iterator var21 = locationMap.keySet().iterator();

                GraphicInfo newTaskGraphicInfo;
                while(var21.hasNext()) {
                    String locationId = (String)var21.next();
                    if (!initialStartEvent.getId().equals(locationId)) {
                        newTaskGraphicInfo = (GraphicInfo)locationMap.get(locationId);
                        newTaskGraphicInfo.setX(newTaskGraphicInfo.getX() + xDiff);
                        newTaskGraphicInfo.setY(newTaskGraphicInfo.getY() + yDiff);
                    }
                }

                Map<String, List<GraphicInfo>> flowLocationMap = bpmnModel.getFlowLocationMap();
                Iterator var28 = flowLocationMap.keySet().iterator();

                label49:
                while(true) {
                    String flowId;
                    do {
                        if (!var28.hasNext()) {
                            GraphicInfo forkGraphicInfo = new GraphicInfo(elementGraphicInfo.getX() + 75.0, elementGraphicInfo.getY() - 5.0, 40.0, 40.0);
                            bpmnModel.addGraphicInfo(parallelGateway.getId(), forkGraphicInfo);
                            bpmnModel.addFlowGraphicInfoList(flowFromStart.getId(), this.createWayPoints(elementGraphicInfo.getX() + 30.0, elementGraphicInfo.getY() + 15.0, elementGraphicInfo.getX() + 75.0, elementGraphicInfo.getY() + 15.0));
                            newTaskGraphicInfo = new GraphicInfo(elementGraphicInfo.getX() + 185.0, elementGraphicInfo.getY() - 163.0, 80.0, 100.0);
                            bpmnModel.addGraphicInfo(userTask.getId(), newTaskGraphicInfo);
                            bpmnModel.addFlowGraphicInfoList(flowToUserTask.getId(), this.createWayPoints(elementGraphicInfo.getX() + 95.0, elementGraphicInfo.getY() - 5.0, elementGraphicInfo.getX() + 95.0, elementGraphicInfo.getY() - 123.0, elementGraphicInfo.getX() + 185.0, elementGraphicInfo.getY() - 123.0));
                            GraphicInfo endGraphicInfo = new GraphicInfo(elementGraphicInfo.getX() + 335.0, elementGraphicInfo.getY() - 137.0, 28.0, 28.0);
                            bpmnModel.addGraphicInfo(endEvent.getId(), endGraphicInfo);
                            bpmnModel.addFlowGraphicInfoList(flowFromUserTask.getId(), this.createWayPoints(elementGraphicInfo.getX() + 285.0, elementGraphicInfo.getY() - 123.0, elementGraphicInfo.getX() + 335.0, elementGraphicInfo.getY() - 123.0));
                            break label49;
                        }

                        flowId = (String)var28.next();
                    } while(flowFromStart.getId().equals(flowId));

                    List<GraphicInfo> flowGraphicInfoList = (List)flowLocationMap.get(flowId);
                    Iterator var25 = flowGraphicInfoList.iterator();

                    while(var25.hasNext()) {
                        GraphicInfo flowGraphicInfo = (GraphicInfo)var25.next();
                        flowGraphicInfo.setX(flowGraphicInfo.getX() + xDiff);
                        flowGraphicInfo.setY(flowGraphicInfo.getY() + yDiff);
                    }
                }
            }
        }
        BaseDynamicSubProcessInjectUtil.processFlowElements(commandContext, process, bpmnModel, originalProcessDefinitionEntity, newDeploymentEntity);
    }

    protected void updateExecutions(CommandContext commandContext, ProcessDefinitionEntity processDefinitionEntity, ExecutionEntity processInstance, List<ExecutionEntity> childExecutions) {
//        ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext);
//        ExecutionEntity execution = executionEntityManager.createChildExecution(processInstance);
//        BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(processDefinitionEntity.getId());
//        UserTask userTask = (UserTask)bpmnModel.getProcessById(processDefinitionEntity.getKey()).getFlowElement(this.dynamicUserTaskBuilder.getDynamicTaskId());
//        execution.setCurrentFlowElement(userTask);
//        Context.getAgenda().planContinueProcessOperation(execution);
    }

    private UserTask initUserTask(FlowElement originalEle){
        UserTask ut = new UserTask();
        ut.setId(dynamicUserTaskBuilder.getId());
        ut.setName(dynamicUserTaskBuilder.getName());
        ut.setAssignee(this.assignee);
        //任务监听-任务启动监听
        List<FlowableListener> taskListeners = new ArrayList<>();
        FlowableListener createListener = new FlowableListener();
        createListener.setEvent("create");
        createListener.setImplementation("${fortressUserTaskCreateListener}");
        createListener.setImplementationType("delegateExpression");
        List<FieldExtension> createFieldExtensions = new ArrayList<>();
        FieldExtension emptyType = new FieldExtension();
        emptyType.setFieldName("emptyType");
        emptyType.setStringValue("TO_PASS");
        createFieldExtensions.add(emptyType);
        createListener.setFieldExtensions(createFieldExtensions);
        taskListeners.add(createListener);
        //获取原节点的任务监听类型
        String refuseTypeValue = "TO_PASS";
        if(originalEle instanceof UserTask){
            List<FlowableListener> flowableListeners = ((UserTask) originalEle).getTaskListeners();
            if(flowableListeners!=null && flowableListeners.size()>0){
                for(FlowableListener fl:flowableListeners){
                    if("complete".equals(fl.getEvent())){
                        List<FieldExtension> fes = fl.getFieldExtensions();
                        for(FieldExtension fe:fes){
                            if("refuseType".equals(fe.getFieldName())){
                                refuseTypeValue = fe.getStringValue();
                            }
                        }
                    }
                }
            }
        }
        //任务监听-任务完成监听
        FlowableListener completeListener = new FlowableListener();
        completeListener.setEvent("complete");
        completeListener.setImplementation("${fortressUserTaskCompleteListener}");
        completeListener.setImplementationType("delegateExpression");
        List<FieldExtension> completeFieldExtensions = new ArrayList<>();
        FieldExtension refuseType = new FieldExtension();
        refuseType.setFieldName("refuseType");
        refuseType.setStringValue(refuseTypeValue);
        completeFieldExtensions.add(refuseType);
        completeListener.setFieldExtensions(completeFieldExtensions);
        taskListeners.add(completeListener);
        ut.setTaskListeners(taskListeners);
        return ut;
    }

    /**
     * namespacePrefix：flowable
     * namespace：http://flowable.org/bpmn
     * @param name
     * @param value
     * @return
     */
    private ExtensionAttribute initExtensionAttribute(String name,String value){
        ExtensionAttribute ea = new ExtensionAttribute();
        ea.setName(name);
        ea.setValue(value);
        ea.setNamespace("http://flowable.org/bpmn");
        ea.setNamespacePrefix("flowable");
        return ea;
    }

    private List<ExtensionAttribute> initEAList(String name,String value){
        List<ExtensionAttribute> list = new ArrayList<>();
        list.add(initExtensionAttribute(name,value));
        return list;
    }

    private SequenceFlow initSequenceFlow(String id,String sourceRef,String targetRef,FlowElement sourceEle,FlowElement targetEle){
        SequenceFlow sequenceFlow = new SequenceFlow();
        sequenceFlow.setId(id);
        sequenceFlow.setSourceRef(sourceRef);
        sequenceFlow.setTargetRef(targetRef);
        sequenceFlow.setSourceFlowElement(sequenceFlow);
        sequenceFlow.setTargetFlowElement(targetEle);
        return sequenceFlow;
    }


}
